﻿//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	MapperClassBuilder.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using Microsoft.CSharp;

namespace Smart.Common.Entities.Mapper
{
	/// <summary>
	/// Mapper class builder
	/// </summary>
	[Localizable(false)]
	public static class MapperClassBuilder
	{
		private static volatile CSharpCodeProvider _csProvider;

		private const string CbNamespace = "Smart.Common.Entities.Mapper";
		private const string CbClassTempl = "public class {0} : {1}";

		[MethodImpl(MethodImplOptions.Synchronized)]
		public static IEntityMapper Generate(Type entityType, MapperClassBuilderConfig config, FieldMapperCacheList mapping)
		{
			if (entityType == null)
				throw new ArgumentNullException("entityType");

			if (entityType.FullName == null)
				throw new ArgumentException("entityType");

			var sw = System.Diagnostics.Stopwatch.StartNew();

			var csb = new List<string>();

			if (_csProvider == null)
				_csProvider =
					new CSharpCodeProvider(
						new Dictionary<string, string>
							{
								{"CompilerVersion", "v3.5"}
							});

			var etfn = _csProvider.GetTypeOutput(new CodeTypeReference(entityType));

			var ul =
				new HashSet<string>(StringComparer.Ordinal)
					{
						"using System;",
						"using System.Linq;",
						"using Smart.Common;",
						"using Smart.Common.Entities;"
					};
			ul.AddRange(config.Usings.SplitByLines());

			var className = entityType.Name + "Mapper" + entityType.FullName.GetHashCodeX();

			csb.AddRange(ul);
			csb.Add(string.Empty);
			csb.Add("namespace " + CbNamespace);
			csb.Add("{");
			csb.Add("\t" + string.Format(CbClassTempl, className, config.BaseInterface));
			csb.Add("\t{");

			// CreateEntity method
			csb.Add("\t\tpublic Entity CreateEntity(Type entityType)");
			csb.Add("\t\t{");
			csb.Add(string.Format("\t\t\treturn new {0}();", etfn));
			csb.Add("\t\t}");

			// FillEntity method
			csb.Add("\t\tpublic bool FillEntity(Entity entity)");
			csb.Add("\t\t{");
			csb.Add(string.Format("\t\t\tvar e = ({0})entity;", etfn));
			//csb.Add("\t\t\te.ID = Identifier.Empty;");
			csb.AddRange(config.PreFill.SplitByLines());
			foreach (var fmi in mapping)
			{
				if (fmi.Direction == FieldMappingDirection.Out)
					continue;

				var templ = config.FillTempls.SafeGet(fmi.MappingType);

				if (!templ.IsNullOrEmpty())
					csb.Add("\t\t\t" + string.Format(templ, fmi.Property.Name));
			}
			csb.AddRange(config.PostFill.SplitByLines());
			csb.Add("\t\t\treturn true;");
			csb.Add("\t\t}");

			// LoadEntity method
			csb.Add("\t\tpublic bool LoadEntity(object source, Entity entity)");
			csb.Add("\t\t{");
			csb.Add(string.Format("\t\t\tvar s = ({0})source;", config.SourceType));
			csb.Add(string.Format("\t\t\tvar e = ({0})entity;", etfn));
			csb.AddRange(config.PreLoad.SplitByLines());
			foreach (var fmi in mapping)
			{
				if (fmi.Direction == FieldMappingDirection.Out)
					continue;

				var templ = config.LoadTempls.SafeGet(fmi.MappingType);

				if (!templ.IsNullOrEmpty())
					csb.Add("\t\t\t" + string.Format(templ, fmi.Property.Name, fmi.FieldName));
			}
			csb.AddRange(config.PostLoad.SplitByLines());
			csb.Add("\t\t\treturn true;");
			csb.Add("\t\t}");

			// SaveEntity method
			csb.Add("\t\tpublic bool SaveEntity(Entity entity, object source)");
			csb.Add("\t\t{");
			csb.Add(string.Format("\t\t\tvar s = ({0})source;", config.SourceType));
			csb.Add(string.Format("\t\t\tvar e = ({0})entity;", etfn));
			csb.AddRange(config.PreSave.SplitByLines());
			foreach (var fmi in mapping)
			{
				if (fmi.Direction == FieldMappingDirection.In)
					continue;

				var templ = config.SaveTempls.SafeGet(fmi.MappingType);

				if (!templ.IsNullOrEmpty())
					csb.Add("\t\t\t" + string.Format(templ, fmi.Property.Name, fmi.FieldName));
			}
			csb.AddRange(config.PostSave.SplitByLines());
			csb.Add("\t\t\treturn true;");
			csb.Add("\t\t}");
			csb.Add("\t}");
			csb.Add("}");

			var assemblyFilter =
				new HashSet<string>(StringComparer.OrdinalIgnoreCase)
					{
						"mscorlib.dll",
						"System.dll",
						"System.Configuration.dll",
						"System.Core.dll",
						"System.Data.dll",
						"System.Data.DataSetExtensions.dll",
						"System.Xml.dll",
						"System.Xml.Linq.dll",
						"System.Web.dll",
						"System.ServiceModel.dll",
						"System.ServiceModel.Web.dll",
						"Microsoft.SharePoint.dll",
						"Microsoft.SharePoint.Security.dll",
						"Smart.Common.dll",
						"Smart.Documents.dll",
						"Smart.Documents.Tests.dll",
					};

			Assembly.GetExecutingAssembly().AddToFileNames(assemblyFilter);
			Assembly.GetCallingAssembly().AddToFileNames(assemblyFilter);
			entityType.Assembly.AddToFileNames(assemblyFilter);

			if (!config.Assemblies.IsNullOrEmpty())
			{
				var aa = config.Assemblies.SplitByLines();

				foreach (var a in aa)
					assemblyFilter.Add(a);
			}

			var al = new List<string>();

			foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
			{
				try
				{
					var fn = assembly.GetFileName();

					if (fn != null && assemblyFilter.Contains(fn))
						al.Add(assembly.Location);
				}
				catch (NotSupportedException ex)
				{
					H.LT("MapperClassBuilder.Generate.NotSupportedException: " + ex.Message);
				}
				catch (Exception ex)
				{
					H.LX("MapperClassBuilder.Generate", ex);
				}
			}

			#region commented

			//var al = new List<string>
			//            {
			//                "mscorlib.dll",
			//                "System.dll",
			//                "System.Configuration.dll",
			//                "System.Core.dll",
			//                "System.Data.dll",
			//                "System.Data.DataSetExtensions.dll",
			//                "System.Xml.dll",
			//                "System.Xml.Linq.dll",
			//                "System.Web.dll",
			//                "System.ServiceModel.dll",
			//                "System.ServiceModel.Web.dll",
			//                "Microsoft.SharePoint.dll",
			//                "Microsoft.SharePoint.Security.dll",
			//                "Smart.Common.dll"
			//            };
			//var al = new List<string>
			//            {
			//                "mscorlib",
			//                "WindowsBase",
			//                "System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
			//                "System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
			//                "System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
			//                "System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
			//                "System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089",
			//                "System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
			//                "System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089",
			//                "System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
			//                "System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
			//                "System.ServiceModel.Web, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
			//                "Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c",
			//                "Microsoft.SharePoint.Security, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c",
			//                "Smart.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9ecbff902332c6b0"
			//            };

			#endregion

			var cp =
				new CompilerParameters(al.ToArray())
					{
						GenerateExecutable = false,
						GenerateInMemory = true,
						TreatWarningsAsErrors = false
					};

			var cr = _csProvider.CompileAssemblyFromSource(cp, csb.CastToString("\r\n"));

			if (cr.Errors.Count > 0)
			{
				foreach (CompilerError ce in cr.Errors)
					H.LX("MapperClassBuilder.Compiler", new InvalidOperationException(ce.ToString()));

				throw new ExecutionEngineException("MapperClassBuilder.Compile");
			}

			var cbt = cr.CompiledAssembly.GetType(CbNamespace + "." + className);

			var instance = (IEntityMapper)Activator.CreateInstance(cbt);

			sw.Stop();
			H.LT("MapperClassBuilder.Generate: compiled EntityMapper class: {0}, time: {1}!", entityType.FullName, sw.Elapsed);

			return instance;
		}
	}
}